<?php
namespace hid;

use hid\base\IdStorage;
use hid\base\IdBuilder;
use Exception;

/**
 * Id管理器
 *<B>说明：</B>
 *<pre>
 * 略
 *</pre>
 * @method IdBuilder|string redis(string $name)
 * @method IdBuilder|string apcu(string $name)
 * @method IdBuilder|string db(string $name)
 * @method IdBuilder|string database(string $name)
 * @method IdBuilder|string memory(string $name)
 * @method IdBuilder|string uuid()
 * @method IdBuilder|string snowflake()
 * @method \hid\builders\SegmentBuilder|string redisSegment(string $name)
 * @method \hid\builders\SegmentBuilder|string databaseSegment(string $name)
 */
class IdManager
{

    /**
     * 默认生成器
     *<B>说明：</B>
     *<pre>
     *  略
     *</pre>
     * @var string
     */
    protected $defaultBuilders = [
        'apcu'=>[
            'storage'=>'apcu'
        ],
        'memory'=>[
            'storage'=>'memory'
        ],
        'uuid'=>[
            'class'=>'hid\builders\UuidBuilder'
        ],

        'snowflake'=>[
            'class'=>'hid\builders\SnowflakeBuilder'
        ],
    ];

    /**
     * id生成器集合
     * @var array
     */
    protected $idBuilders = [
//        'orderid' => ['idname'=>'hehe','step'=>1,'storageType'=>'redis'],
    ];

    /**
     * id生成器对象缓存
     * @var IdBuilder[]
     */
    protected $idBuilderCaches = [];


    /**
     * id存储器配置
     *<B>说明：</B>
     *<pre>
     *  略
     *</pre>
     * @var array
     */
    protected $storages = [
//        'redis' => [],
    ];

    /**
     * id存储器对象缓存
     *<B>说明：</B>
     *<pre>
     *  略
     *</pre>
     * @var IdStorage[]
     */
    protected $storageCaches = [];

    /**
     * 支持的模版变量集合
     * @var array
     */
    protected $templateVars = [];

    protected $idKeyCaches = [];

    /**
     * 构造方法
     *<B>说明：</B>
     *<pre>
     *  略
     *</pre>
     * @param array $config
     */
    public function __construct(array $config = [])
    {
        if (!empty($config)) {
            foreach ($config as $key => $value) {
                $this->$key = $value;
            }
        }

        $this->idBuilders = array_merge($this->defaultBuilders,$this->idBuilders);
    }


    /**
     * 注册id存储器
     * @param string $storageType id存储器类型
     * @param array $config 配置
     */
    public function registerIdStorage(string $storageType,array $config):self
    {
        $this->storages[$storageType] = $config;
        return $this;
    }

    /**
     * 注册id生成器
     * @param string $idBuilderType
     * @param array $config
     */
    public function registerIdBuilder(string $idBuilderType,array $config):self
    {
        $this->idBuilders[$idBuilderType] = $config;
        return $this;
    }

    /**
     * 获取id生成器单例对象
     * @param string $idBuilderType
     * @return IdBuilder
     */
    public function getIdBuilder(string $idBuilderType = ''):IdBuilder
    {
        if (!isset($this->idBuilderCaches[$idBuilderType])) {
            $this->idBuilderCaches[$idBuilderType] = $this->makeIdBuilder($idBuilderType);
        }

        return $this->idBuilderCaches[$idBuilderType];
    }

    /**
     * 创建id生成器对象
     * @param string $idBuilderType
     * @return IdBuilder
     */
    public function makeIdBuilder(string $idBuilderType = ''):IdBuilder
    {
        $idBuilderClass = IdBuilder::class;
        $idBuilderConfig = [];
        if (isset($this->idBuilders[$idBuilderType])) {
            $idBuilderConfig = $this->idBuilders[$idBuilderType];
            if (isset($idBuilderConfig['class'])) {
                $idBuilderClass = $idBuilderConfig['class'];
            }
        }

        $idBuilderConfig['idManager'] = $this;
        return new $idBuilderClass($idBuilderConfig);
    }


    /**
     * 获取Id存储器单例对象
     *<B>说明：</B>
     *<pre>
     *  略
     *</pre>
     * @param string $storageType 存储类型,如不填或空,则使用默认存储对象
     * @return IdStorage
     * @throws Exception 存储配置找不到时跑出异常
     */
    public function getIdStorage(string $storageType = ''):IdStorage
    {
        if (isset($this->storageCaches[$storageType])) {
            return $this->storageCaches[$storageType];
        }

        if (!isset($this->storages[$storageType])) {
            throw new Exception(sprintf("id %s storage no find",$storageType));
        }

        $storageConfig = $this->storages[$storageType];

        if (isset($storageConfig['class'])) {
            $storageClass = $storageConfig['class'];
            if (strpos($storageClass,'\\') === false) {
                $storageClass = __NAMESPACE__ . '\\storages\\'.$storageClass.'\\'.$storageClass . 'Storage';
            }
            unset($storageConfig['class']);
        } else {
            $storageClass = __NAMESPACE__ . '\\storages\\'.$storageType.'\\'.ucfirst($storageType) . 'Storage';
        }

        $this->storageCaches[$storageType] = new $storageClass($storageConfig);;

        return $this->storageCaches[$storageType];
    }

    public function __call($idBuilderType, $parameters)
    {
        $name = '';
        if (!empty($parameters)) {
            $name = $parameters[0];
        }

        if ($name !== '') {
            $idBuilder = $this->makeIdBuilder($idBuilderType);
            $idBuilder->setName($name);
            return $idBuilder;
        } else {
            return $this->getIdBuilder($idBuilderType);
        }
    }

    public function __get($idBuilderType)
    {
        return $this->getIdBuilder($idBuilderType);
    }


    /**
     * 添加模版变量
     * @param string $var 变量名
     * @param string|callable|array $value 变量值
     */
    public function addTemplateVar(string $var,$value):self
    {
        $this->templateVars[$var] = $value;

        return $this;
    }

    /**
     * 解析模板
     * @param string $template 模版字符串
     * @return array 格式:[替换模板, 模板变量]
     */
    protected function parseTemplate(string $template):array
    {
        $tplReg = '/\{([a-zA-Z0-9]+):?([^\}]+)?\}/';
        $matches = [];
        $tagIndex = 0;
        $replaceTemplate = preg_replace_callback($tplReg,function($matches) use (&$tagIndex){
            $tagname = '<'.$matches[1] . $tagIndex . '>';
            $tagIndex++;
            return $tagname;
        },$template);

        $templateVars = [];
        if (preg_match_all($tplReg, $template, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) {
            foreach ($matches as $index=>$param) {
                $name = $param[1][0];
                if (isset($param[2][0])) {
                    $func_params = explode(',',$param[2][0]);
                } else {
                    $func_params = [];
                }
                // 判断是否有参数
                $tagName = '<' .$name . $index .  '>';
                $templateVars[] = [$name,$tagName,$func_params];
            }
        }

        return [$replaceTemplate,$templateVars];
    }

    public function buildKey(string $tplKey,array $keyVars = []):string
    {
        if (!isset($this->idKeyCaches[$tplKey])) {
            $this->idKeyCaches[$tplKey] = $this->parseTemplate($tplKey);
        }

        list($cacheKey,$cacheKeyVars) = $this->idKeyCaches[$tplKey];
        $keyVars = array_merge($this->templateVars,$keyVars);
        $replaceParams = [];
        foreach ($cacheKeyVars as $var) {
            list($name,$key,$funcParams) = $var;
            $is_function = false;
            if (isset($keyVars[':'.$name])) {
                $name = ':'.$name;
                $is_function = true;
            }

            if (isset($keyVars[$name])) {
                $value = $keyVars[$name];
                if (is_array($value) || $is_function === true) {
                    $funcParams = is_string($funcParams) ? explode(',',$funcParams) : $funcParams;
                    $replaceParams[$key] = call_user_func_array($value,$funcParams);
                } else {
                    $replaceParams[$key] = $value;
                }
            }
        }

        return strtr($cacheKey,$replaceParams);
    }
}
